/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023.
 * Description: tpm decoupling queue implementation
 * Author: zhuyan
 * Create: 2023-10-25
 */
#include <linux/sched/clock.h>

LIST_HEAD(ima_default_measurements); /* list of default policy's measurements */
int ima_default_measurements_status; /* the status of ima_default_measurements */

/*
 * Add template entry of default policy rules to ima_default_measurements.
 */
int ima_get_default_measurements(struct ima_template_entry *entry)
{
	struct ima_queue_entry *qe_default;

	qe_default = kmalloc(sizeof(*qe_default), GFP_KERNEL);
	if (qe_default == NULL) {
		pr_err("OUT OF MEMORY ERROR creating queue entry\n");
		return -ENOMEM;
	}

	INIT_LIST_HEAD(&qe_default->later);
	qe_default->entry = entry;
	list_add_tail_rcu(&qe_default->later, &ima_default_measurements);
	return 0;
}

static void ima_delete_default_measurements(void)
{
	struct ima_queue_entry *entry = NULL, *tmp = NULL;

	list_for_each_entry_safe(entry, tmp, &ima_default_measurements, later) {
		list_del(&entry->later);
		kfree(entry);
	}
}

#ifdef CONFIG_RTOS_CIV_DIGEST_LIST
/*
 * Find the entry whose digest of file is included in digest_list_data.
 */
static struct ima_digest *ima_found_digest(struct ima_template_entry *entry)
{
	struct ima_digest *found_digest = NULL;
	u8 *buf_ptr = entry->template_data[0].data;
	u32 buflen = entry->template_data[0].len;

	if (buflen != 0) {
		buf_ptr = strnchr(entry->template_data[0].data, buflen, ':');
		buf_ptr += 2;
		buflen -= buf_ptr - entry->template_data[0].data;
		if (buflen != 0)
			found_digest = ima_lookup_digest(buf_ptr, ima_hash_algo);
	}
	return found_digest;
}
#endif

/*
 * Add template entry of ima_default_measurements to the measurement list
 * and hash table, and extend the pcr.
 */
static int ima_add_default_template_entry(struct ima_template_entry *entry,
					  const unsigned char *filename, const char *op)
{
	int result = 0, tpmresult = 0;
	struct tpm_digest *digests_arg = entry->digests;
	char tpm_audit_cause[AUDIT_CAUSE_LEN_MAX];
	const char *audit_cause = NULL;
	int audit_info = 1;

	result = ima_add_digest_entry(entry, 1);
	if (result < 0)
		return result;

#ifdef CONFIG_RTOS_IMA_MEASURE_THRESHOLD
	tpmresult = ima_pcr_extend_within_threshold(digests_arg, entry->pcr);
#else
	tpmresult = ima_pcr_extend(digests_arg, entry->pcr);
#endif
	if (tpmresult != 0) {
		snprintf(tpm_audit_cause, AUDIT_CAUSE_LEN_MAX, "TPM_error(%d)",
			 tpmresult);
		audit_cause = tpm_audit_cause;
		audit_info = 0;
		integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, filename,
				    op, audit_cause, result, audit_info);
	}

	return result;
}

/*
 * Add template entry of ima_default_measurements to the measurement list
 * and hash table, and extend the pcr. The template entry whose digest of file
 * is included in digest_list_data won't be add into the measurement list and
 * hash table, and extend the pcr.
 */
void ima_move_default_measurements(void)
{
	int result = 0;
	const char *op = "move_default_measurements";
	const char *audit_cause = "moving_error";
	int audit_info = 1;
	const unsigned char *filename = NULL;
	u64 start_clock = 0, move_time = 0;
	struct ima_queue_entry *qe_default = NULL;
	struct ima_template_entry *entry = NULL;
#ifdef CONFIG_RTOS_CIV_DIGEST_LIST
	struct ima_digest *found_digest = NULL;
#endif

	mutex_lock(&ima_extend_list_mutex);
	start_clock = sched_clock();
	list_for_each_entry(qe_default, &ima_default_measurements, later) {
		/* get entry */
		entry = qe_default->entry;
		if (!entry)
			goto out;
		filename = entry->template_data[1].data;

#ifdef CONFIG_RTOS_CIV_DIGEST_LIST
		found_digest = ima_found_digest(entry);
		if (found_digest)
			continue;
#endif
		result = ima_add_default_template_entry(entry, filename, op);
		if (result < 0) {
			audit_cause = "ENOMEM";
			audit_info = 0;
			integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, filename,
					    op, audit_cause, result, audit_info);
			goto out;
		}
	}
out:
	move_time = sched_clock() - start_clock;
	do_div(move_time, 1000000);
	if (move_time != 0)
		pr_info("Finish moving ima_default_measurements: move default measurements cost %llu ms\n",
			move_time);
	ima_delete_default_measurements();
	mutex_unlock(&ima_extend_list_mutex);
	/* only move ima_default_measurements to ima_measurements once */
	ima_default_measurements_status = IMA_DEFAULT_MEASUREMENTS_MOVED;
}
